﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.IO;

namespace LaTeXGrapher
{
    public class FunctionContext
    {
        int _minMaxCount = 0;
        public Point Min { get; private set; }
        public Point Max { get; private set; }
        public Vector TickInterval { get; private set; }

        ExpressionParser _parser = new ExpressionParser();

        Dictionary<string, IFunction2d> _map = new Dictionary<string,IFunction2d>();
        Dictionary<string, Delegate> _mapNamed = new Dictionary<string, Delegate>();
        
        List<LineData> _lines = new List<LineData>();
        List<PointData> _points = new List<PointData>();
        List<ShadeData> _shades = new List<ShadeData>();
        List<IFunction2d> _plotFunctions = new List<IFunction2d>();

        public IEnumerable<LineData> Lines { get { return _lines.AsReadOnly(); } }
        public IEnumerable<PointData> Points { get { return _points.AsReadOnly(); } }
        public IEnumerable<ShadeData> Shades { get { return _shades.AsReadOnly(); } }
        public IEnumerable<IFunction2d> PlotFunctions { get { return _plotFunctions.AsReadOnly(); } }
        
        public FunctionContext()
        {
            TickInterval = new Vector(1, 1);
            Min = new Point(-10, -10);
            Max = new Point(10, 10);
        }

        public System.Windows.Vector? MarkTick(bool bX, double t, double tickWidth)
        {
            return null;
        }

        public bool ContainsIFunction2d(string name)
        {
            return _map.ContainsKey(name);
        }

        public IFunction2d GetIFunction2d(string name)
        {
            return _map[name];
        }

        public void AddIFunction2d(string name, IFunction2d func)
        {
            _map[name] = func;
        }

        public Delegate LookupF(string name)
        {
            return _mapNamed[name];
        }

        public void SetView(IEnumerable<KeyValuePair<string, string>> viewData)
        {
            foreach (var pair in viewData)
                SetView(new Point(Evaluate(pair.Key), Evaluate(pair.Value)));
        }

        public void SetView(Point pt)
        {
            // The idea with this implementation is to never leave the view in an "invalid" state.
            if (_minMaxCount == 0)
            {
                Min = pt;
                Max = pt + new Vector(10,10);
                _minMaxCount++;
                return;
            }
            else if (_minMaxCount == 1)
            {
                Max = Min;
                _minMaxCount++;
            }
            
            Max = Maximize(Max, pt);
            Min = Minimize(Min, pt);
        }

        Point Maximize(Point a, Point b)
        {
            return new Point(Math.Max(a.X, b.X), Math.Max(a.Y, b.Y));
        }

        Point Minimize(Point a, Point b)
        {
            return new Point(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y));
        }

        public void LoadFunctions(IEnumerable<FunctionData> functions)
        {
            foreach (var data in functions)
            {
                Func<double[], double> f = Compile(data);
                _parser.SetUserFunction(data.Name, f); // Add this named function to the lookup list.

                // In the old code we needed to handle all sorts of arguments, but
                // now we only need to put functions of one argument in _mapNamed.
                // So when we refactor we can keep that in mind.
                _mapNamed[data.Name] = ConvertToFixedArguments(data.Arguments.Count(), f);
            }
        }

        static Delegate ConvertToFixedArguments(int count, Func<double[], double> f)
        {
            // TODO: Quick DLR to the rescue!?!
            switch (count)
            {
                case 0:
                    return (Func<double>)(() => f(new double[] { }));
                case 1:
                    return (Func<double, double>)(a => f(new double[] { a }));
                case 2:
                    return (Func<double, double, double>)((a, b) => f(new double[] { a, b }));
                case 3:
                    return (Func<double, double, double, double>)((a, b, c) => f(new double[] { a, b, c }));
                case 4:
                    return (Func<double, double, double, double, double>)((a, b, c, d) => f(new double[] { a, b, c, d }));
            }
            throw new NotSupportedException("Functions with more than four arguments are not supported.");
        }

        static IDictionary<string, double> MakeArgumentMap(IEnumerable<string> keys, double[] values)
        {
            var map = new Dictionary<string, double>();
            int i = 0;
            foreach (var k in keys)
                map[k] = values[i++];
            return map;
        }

        public Func<double[], double> Compile(FunctionData functionData)
        {
            return a =>
                {
                    foreach (var pair in functionData.Expressions)
                    {
                        if (string.IsNullOrEmpty(pair.Value))
                            return _parser.EvaluateWithArguments(pair.Key, MakeArgumentMap(functionData.Arguments, a));
                        if (_parser.EvaluateConditionalWithArguments(pair.Value, MakeArgumentMap(functionData.Arguments, a)))
                            return _parser.EvaluateWithArguments(pair.Key, MakeArgumentMap(functionData.Arguments, a));
                    }
                    throw new InvalidOperationException("Compiled function failed to return.");
                };
        }

        public IEnumerable<double> EvaluateSpecial(List<string> _special)
        {
            return _special.Select(s => Evaluate(s));
        }

        public double Evaluate(string expression)
        {
            return _parser.EvaluateWithArguments(expression, null);
        }

        public void SetLines(IEnumerable<LineData> lines)
        {
            _lines.AddRange(lines);
        }

        public void SetPoints(IEnumerable<PointData> points)
        {
            _points.AddRange(points);
        }

        public void SetShades(IEnumerable<ShadeData> shades)
        {
            _shades.AddRange(shades);
        }

        public void SetPlotFunctions(IEnumerable<IFunction2d> functions)
        {
            _plotFunctions.AddRange(functions);
        }
    }
}
